//=============================================================================
// System  : MatchKit
// File    : BasePattern.cs
// Author  : Fabio Bussu  (matchkit@fabiobussu.it)
// Updated : 04/16/2013
// Note    : Copyright 2013, Fabio Bussu, All rights reserved
//
// This code is published under the Microsoft Public License (Ms-PL). A copy
// of the license should be distributed with the code.  It can also be found
// at the project website: http://matchkit.codeplex.com. This notice, the
// author's name, and all copyright notices must remain intact in all
// applications, documentation, and source files.
//
// Version     Date     Who  Comments
// ============================================================================
// 1.0.0.0  04/16/2013   FB  First draft
//=============================================================================
using System;
using System.Collections.Generic;
using System.Runtime.Serialization;

#pragma warning disable 1591
#pragma warning disable 1573

namespace MatchKit.Patterns
{
	/// <summary>
	/// Provides common base pattern implementation for the matching infrastructure. All the
	/// patterns exposed by the MatchKit library are inherited from this type.
	/// </summary>
	/// <threadsafety static="true" instance="false" />
	/// <serialization binary="true" contract="true" />
	/// <conceptualLink target="7e345cd8-5aaa-4616-a3c9-abcc9943dabc" />
	/// <conceptualLink target="7e345cd8-5aaa-58ad-9876-abcc9344dabc" />
	/// <conceptualLink target="0a06b2a9-5f58-409a-ba62-639cfd77bca7" />
	#if DC
	[DataContract]
	#endif
	#if SZ
	[Serializable]
	#endif
	public abstract class BasePattern : IPattern
	{
		/// <summary>
		/// Initializes a new instance of the <see cref="T:MatchKit.Patterns.BasePattern"/> class. 
		/// </summary>
		public BasePattern()
		{
			// empty
		}

		/// <inheritdoc />
		/// <serialization member="true" />
		#if DC
		[DataMember]
		#endif
		public string CaptureName
		{
			get; set;
		}

		/// <inheritdoc />
		/// <serialization member="true" />
		/// <conceptualLink target="7e345cd8-5aaa-58ad-9876-abcc9344dabc" />
		#if DC
		[DataMember]
		#endif
		public bool IsPointOfNoReturn { get; set; }

		/// <inheritdoc />
		/// <serialization member="false" />
		public virtual IEnumerable<IPattern> Children
		{
			get { return null; }
		}

		/// <inheritdoc />
		/// <serialization member="false" />
		public virtual bool HasChildren
		{
			get { return false; }
		}

		/// <inheritdoc />
		public virtual Match Match(MatchContext context)
		{
			if (context == null)
				throw new ArgumentNullException("context", "context cannot be null");

			if (context.IsTimedOut)
				throw new MatchTimeoutException();

			if (context.IsCancellationRequested)
				throw new MatchCanceledException();
			
			var tracer = context.Tracer;

			if (tracer != null)
			{
				IMatchTracerExtensions.BeginMatch(tracer, context, this);
			}

			// salvo il location prima del match
			var location = context.Location;

			var result = OnMatch(context);

			if (result.Location.Index == 0)
				result.Location = location;

			if (result.Success)
			{
				result.MatchLength = context.Index - location.Index;
			}

			if (tracer != null)
			{
				IMatchTracerExtensions.MatchComplete(tracer, context, result);
			}

			return result;
		}

		/// <summary>
		/// Perform the match logic.
		/// </summary>
		/// <param name="context">Matching context.</param>
		/// <returns>The resulted match.</returns>
		protected abstract Match OnMatch(MatchContext context);

		/// <inheritdoc />
		/// <serialization member="true" />
		#if DC
		[DataMember]
		#endif
		public object UserData { get; set; }

		/// <inheritdoc />
		/// <serialization member="false" />
		public abstract string HelpString { get; }

		/// <summary>
		/// Throws an exception of type <see cref="T:MatchKit.MatchFailedException"/>.
		/// </summary>
		/// <param name="match">The match object with error details.</param>
		protected static void ThrowPointOfNoReturnReached(Match match)
		{
			throw new MatchFailedException(27, match, "Match failed for point of no return reached");
		}

		/// <summary>
		/// Creates a match object with error details.
		/// </summary>
		/// <param name="context">The match context.</param>
		/// <returns>The match with error details.</returns>
		protected Match Error(MatchContext context)
		{
			return Error(context, null);
		}

		/// <summary>
		/// Creates a match object with error details.
		/// </summary>
		/// <param name="context">The match context.</param>
		/// <param name="errorMessage">Custom error message.</param>
		/// <returns>The match with error details.</returns>
		protected Match Error(MatchContext context, string errorMessage)
		{
			if (context == null)
				throw new ArgumentNullException("context", "context cannot be null");
			return new Match(context, this, false)
			{
				ErrorString = errorMessage ?? "Expected " + HelpString
			};
		}

		/// <summary>
		/// Creates a match object with error details and optional flag setted.
		/// </summary>
		/// <param name="context">The match context.</param>
		/// <param name="inner">Child match in error. This value can be null.</param>
		/// <returns>The match with error details.</returns>
		/// <remarks>
		/// This method must be invoked to create a match result from a pattern that has failed an optional match.
		/// </remarks>
		protected Match Optional(MatchContext context, Match inner)
		{
			return Optional(context, inner, null);
		}

		/// <summary>
		/// Creates a match object with error details and optional flag setted.
		/// </summary>
		/// <param name="context">The match context.</param>
		/// <param name="inner">Child match in error. This value can be null.</param>
		/// <param name="errorMessage">Custom error message.</param>
		/// <returns>The match with error details.</returns>
		protected Match Optional(MatchContext context, Match inner, string errorMessage)
		{
			if (context == null)
				throw new ArgumentNullException("context", "context cannot be null");
			var m = Error(context, errorMessage);
			if (inner != null)
				m.Children = new Match[] { inner };
			m.IsOptional = true;
			return m;
		}

		/// <summary>
		/// Creates a successful match object.
		/// </summary>
		/// <param name="context">The match context.</param>
		/// <param name="typedValue">Custom value parsed from the match.</param>
		/// <returns>The match with error details.</returns>
		/// <remarks>
		/// The parameter <paramref name="typedValue"/> can be any type of value; this is dependent from the 
		/// scope of the pattern. For example, a NumberPattern will be a decimal or long value and a 
		/// HexPattern will be a byte array value.
		/// <br />
		/// When a pattern that inherits from <see cref="T:MatchKit.Patterns.BasePattern"/> has no specific value, 
		/// the parameter <paramref name="typedValue"/> can be pushed with <see cref="P:MatchKit.Patterns.MatchedString.Value"/>, 
		/// so the <b>BasePattern</b> common implementation retrieve the matched string.
		/// </remarks>
		protected Match Success(MatchContext context, object typedValue)
		{
			return Success(context, typedValue, (Match[])null);
		}

		/// <inheritdoc cref="M:MatchKit.Patterns.BasePattern.Success(MatchKit.MatchContext,System.Object)" select="summary|remarks|param|returns" />
		/// <param name="child">Child match.</param>
		protected Match Success(MatchContext context, object typedValue, Match child)
		{
			return Success(context, typedValue, new Match[] { child });
		}

		/// <inheritdoc cref="MatchKit.Patterns.BasePattern.Success(MatchContext,object)" select="summary|remarks|param|returns" />
		/// <param name="children">Children matches.</param>
		protected Match Success(MatchContext context, object typedValue, Match[] children)
		{
			if (context == null)
				throw new ArgumentNullException("context", "context cannot be null");
			return new Match(context, this, true)
			{
				Children = children,
				Value = typedValue
			};
		}
	}
}
